﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using Elderos.Ontology;
using Elderos.Utils;

namespace Elderos.Highlight.Search
{
    [DataContract]
    public class Position : IEquatable<Position>
    {
        private Position(int start, int length)
        {
            CharInterval = new Interval<int>(start, start + length - 1);
        }

        public Position(int start, int length, IEnumerable<int> entityIDs, string text)
            : this(start, length)
        {
            Text = text;

            Entities = (from entityID in entityIDs
                                select new TextEntity(entityID, this)).ToList();
        }

        public static Position CreateFake(int start, int length, string text)
        {
            var pos = new Position(start, length, new int[0], text);
            pos.IsFake = true;
            return pos;
        }

        public bool HasWinner { get { return Winner != null; } }

        [DataMember]
        public Interval<int> CharInterval { get; private set; }

        public List<TextEntity> EntitiesFlattened
        {
            get
            {
                if (Subpositions == null) return Entities;
                return Entities
                    .Union(Subpositions.SelectMany(x => x.EntitiesFlattened))
                    .ToList();
            }
        }

        [DataMember]
        public List<TextEntity> Entities { get; private set; }

        [DataMember]
        public List<Position> Subpositions
        {
            get { return _subpositions; }
            private set { _subpositions = value; }
        }

        public List<Position> SubpositionsFlattened
        {
            get
            {
                var result = new List<Position>();
                var positions = new Stack<Position>();
                positions.Push(this);
                for (int i = 0; positions.Count > 0; i++)
                {
                    if (i > 10000) 
                        throw new TreeRecursionException("Too many nested position or possibly tree recursion occured.");
                    var pos = positions.Pop();
                    result.Add(pos);
                    foreach (var child in pos.Subpositions)
                    {
                        positions.Push(child);
                    }
                }

                return result;
            }
        }

        [DataMember]
        public string Text { get; private set; }

        [DataMember]
        public Interval<int> TokenInterval { get; internal set; }

        [DataMember]
        public TextEntity Winner { get; set; }

        private List<Position> _subpositions = new List<Position>();

        [Obsolete("Works incorrectly because of nested positions scheme.")]
        public void SetWinnerByID(int? winnerID)
        {
            if (!winnerID.HasValue)
            {
                Winner = null;
                return;
            }
            Winner = EntitiesFlattened.First(x => x.EntityID == winnerID.Value);
        }

        [DataMember]
        public bool IsFake { get; private set; }

        [OnDeserialized]
        private void OnDeserialized(StreamingContext context)
        {
            foreach (TextEntity ownedEntity in Entities)
                ownedEntity.OwnerPosition = this;
        }

        public override string ToString()
        {
            return String.Format("{0}({1}), Count: {2}", CharInterval, Text, Entities.Count) +
                   (Winner != null ? ", Winner:" + Winner : "");
        }

        #region Implementation of IEquatable<Position>

        /// <summary>
        ///     Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <returns>
        ///     true if the current object is equal to the <paramref name="other" /> parameter; otherwise, false.
        /// </returns>
        /// <param name="other">An object to compare with this object.</param>
        public bool Equals(Position other)
        {
            return Equals(this, other);
        }

        #endregion
    }
}